﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using net.xBei.Models;

namespace net.xBei.Helper {
    /// <summary>
    /// IO（文件，路径、流）操作
    /// </summary>
    public static class IOHelper {
        /// <summary>
        /// 重置流索引
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"/>
        public static T Reset<T>(this T stream) where T : Stream {
            stream.Seek(0, SeekOrigin.Begin);
            return stream;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        public static FileSize ToFileSize(this double size) => new FileSize(size);
        /// <summary>
        /// 从流中读取字符串（自动识别编码）
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static string ReadStringAutoDecoding(this Stream stream) {
            var buf = new byte[3];
            var off = stream.Read(buf, 0, 3);
            if (off == 3 && buf[0] == 0xEF && buf[1] == 0xBF && buf[2] == 0xBD) {
                return ReadStringAutoDecoding(stream);
            }
            if (off == 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
                //\xEF\xBB\xBF UTF-8(BOM)
                using var ms = new MemoryStream();
                stream.CopyTo(ms);
                return System.Text.Encoding.UTF8.GetString(ms.ToArray());
            }

            if (off >= 2 && buf[0] == 0xFF && buf[1] == 0xFE) {
                //\xFF\xFE UTF-16 LE
                using var ms = new MemoryStream();
                ms.WriteByte(buf[2]);
                stream.CopyTo(ms);
                return new System.Text.UnicodeEncoding(false, true).GetString(ms.ToArray());
            }
            if (off >= 2 && buf[0] == 0xFE && buf[1] == 0xFF) {
                //\xFE\xFF UTF-16 BE
                using var ms = new MemoryStream();
                ms.WriteByte(buf[2]);
                stream.CopyTo(ms);
                return new System.Text.UnicodeEncoding(true, true).GetString(ms.ToArray());
            }
            {
                using var ms = new MemoryStream();
                ms.Write(buf);
                stream.CopyTo(ms);
                var buffer = ms.ToArray();
                return TestEncoding(buffer, System.Text.Encoding.UTF8).GetString(buffer);
            }
        }
        /// <summary>
        /// 从字节数组中读取字符串（自动识别编码）
        /// </summary>
        /// <param name="buffer"></param>
        /// <returns></returns>
        public static string ReadStringAutoDecoding(this IEnumerable<byte> buffer) {
            var off = buffer.Count();
            var buf = buffer.Take(3).ToArray();
            if (off >= 3 && buf[0] == 0xEF && buf[1] == 0xBF && buf[2] == 0xBD) {
                return ReadStringAutoDecoding(buffer.Skip(3));
            }
            if (off >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
                //\xEF\xBB\xBF UTF-8(BOM)
                return System.Text.Encoding.UTF8.GetString(buffer.Skip(3).ToArray());
            }

            if (off >= 2 && buf[0] == 0xFF && buf[1] == 0xFE) {
                //\xFF\xFE UTF-16 LE
                return new System.Text.UnicodeEncoding(false, true).GetString(buffer.Skip(2).ToArray());
            }
            if (off >= 2 && buf[0] == 0xFE && buf[1] == 0xFF) {
                //\xFE\xFF UTF-16 BE
                return new System.Text.UnicodeEncoding(true, true).GetString(buffer.Skip(2).ToArray());
            }
            var arr = buffer.ToArray();
            return TestEncoding(arr, System.Text.Encoding.UTF8).GetString(arr);
        }
        /// <summary>
        /// 测试字符串的编码
        /// </summary>
        /// <param name="data"></param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public static System.Text.Encoding TestEncoding(this byte[] data, System.Text.Encoding encoding) {
            using var stream = new MemoryStream(data);
            Ude.CharsetDetector cdet = new();
            cdet.Feed(stream);
            cdet.DataEnd();
            return cdet.Charset != null ? System.Text.Encoding.GetEncoding(cdet.Charset) : encoding;
        }

        /// <summary>
        /// 从<paramref name="stream"/>中读取<paramref name="readLength"/>个字节
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="readLength"></param>
        /// <returns></returns>
        public static async Task<(int count, byte[] buffer)> ReadBytesAsync(this Stream stream, int readLength) {
            byte[] bytes = new byte[readLength];
            var read = await stream.ReadAsync(bytes);
            return (read, bytes);
        }


        /// <summary>
        /// 用“共享读”模式打开文件并读取所有内容到内存
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static byte[] ReadFile(string file) {
            using var readStream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
            using var str = new MemoryStream();
            readStream.CopyTo(str);
            return str.ToArray();
        }
        /// <summary>
        /// 用“共享读”模式打开文件并读取所有内容到内存
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static async Task<MemoryStream> ReadFileAsync(string file) {
            using var readStream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
            MemoryStream ms = new();
            await readStream.CopyToAsync(ms, 1024);
            ms.Reset();
            return ms;
        }
        /// <summary>
        /// 是否文件
        /// </summary>
        /// <param name="path"></param>
        /// <param name="fileInfo"></param>
        /// <returns></returns>
        public static bool IsFile(string path, out FileInfo fileInfo) {
            // if (string.IsNullOrWhiteSpace(path)) return false;
            fileInfo = new FileInfo(path);
            if (!fileInfo.Exists) return false;
            if ((fileInfo.Attributes & FileAttributes.Archive) == FileAttributes.Archive && fileInfo.Length > 0) return true;
            if ((fileInfo.Attributes & FileAttributes.Normal) == FileAttributes.Normal && fileInfo.Length > 0) return true;
            return fileInfo.Length > 0;
        }

        /// <summary>
        /// 获取<paramref name="folder"/>目录下的所有文件并使用<paramref name="predicate"/>过滤，
        /// 忽略<see cref="UnauthorizedAccessException"/>异常
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="predicate">过滤器</param>
        /// <returns></returns>
        public static IEnumerable<string> GetFiles(string folder, Func<string, bool>? predicate = null) {
            try {
                return (predicate == null
                        ? Directory.EnumerateFiles(folder)
                        : Directory.EnumerateFiles(folder).Where(predicate))
                    .Select(file => Path.GetFullPath(file));
            } catch (UnauthorizedAccessException) {
                return Array.Empty<string>();
            }
        }
        /// <summary>
        /// 获取<paramref name="folder"/>目录下的所有文件返回前使用<paramref name="predicate"/>过滤，忽略<see cref="UnauthorizedAccessException"/>
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public static IEnumerable<string> GetDirectories(string folder, Func<string, bool>? predicate = null) {
            try {
                return predicate == null
                    ? Directory.EnumerateDirectories(folder)
                    : Directory.EnumerateDirectories(folder).Where(predicate);
            } catch (UnauthorizedAccessException) {
                return Array.Empty<string>();
            }
        }
        /// <summary>
        /// 判断<paramref name="file"/>是否是指定的文件类型（通过后缀名判断，不区分大小写，参考：<seealso cref="StringComparison.OrdinalIgnoreCase"/>）
        /// </summary>
        /// <param name="file"></param>
        /// <param name="fileTypes">后缀名（带“.”前缀）例如：“.txt”</param>
        /// <returns></returns>
        public static bool IsFileType(this string file, params string[] fileTypes)
            => !string.IsNullOrWhiteSpace(file)
                    && fileTypes.Any() != false
                    && fileTypes.Any(ft => file.EndsWith(ft, StringComparison.OrdinalIgnoreCase));
        /// <summary>
        /// 判断<paramref name="file"/>是否是指定的文件类型（通过后缀名判断，不区分大小写，参考：<seealso cref="StringComparison.OrdinalIgnoreCase"/>）
        /// </summary>
        /// <param name="file"></param>
        /// <param name="fileTypes">后缀名（带“.”前缀）例如：“.txt”</param>
        /// <returns></returns>
        public static bool IsFileType(this FileInfo file, params string[] fileTypes)
            => file != null
                    && !string.IsNullOrWhiteSpace(file.Extension)
                    && fileTypes.Any() != false
                    && fileTypes.Any(ft => file.Extension.Equals(ft, StringComparison.OrdinalIgnoreCase));
        /// <summary>
        /// 判断文件（或目录）是否存在
        /// </summary>
        /// <param name="fileOrFolder"></param>
        /// <returns></returns>
        public static bool IsExists(string fileOrFolder) => File.Exists(fileOrFolder) || Directory.Exists(fileOrFolder);
        /// <summary>
        /// 判断文件（或目录）是否存在
        /// </summary>
        /// <param name="fileOrFolder"></param>
        /// <param name="fileSystemInfo"></param>
        /// <returns></returns>
        public static bool IsExists(string fileOrFolder, [NotNullWhen(true)] out FileSystemInfo? fileSystemInfo) {
            if (File.Exists(fileOrFolder)) {
                fileSystemInfo = new FileInfo(fileOrFolder);
                return true;
            }
            if (Directory.Exists(fileOrFolder)) {
                fileSystemInfo = new DirectoryInfo(fileOrFolder);
                return true;
            }
            fileSystemInfo = null;
            return false;
        }
        /// <summary>
        /// 删除“空白”（没有子目录和文件）目录
        /// </summary>
        /// <param name="folder"></param>
        public static void RemoveBlankFolder(string folder) {
            var isBlank = !Directory.GetFiles(folder).Any();
            var folders = Directory.GetDirectories(folder);
            foreach (var item in folders) {
                RemoveBlankFolder(item);
            }
            if (isBlank && Directory.GetDirectories(folder).Length == 0) {
                Directory.Delete(folder, false);
            }
        }
        /// <summary>
        /// 删除文件
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static bool DeleteFile(string filePath) {
            if (File.Exists(filePath) == false) return true;
            try {
                File.Delete(filePath);
            } catch {
                return false;
            }
            return true;
        }

        /// <summary>
        /// 安全的删除目录
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static void SafeRemoveFolder(string path) {
            var info = new FileInfo(path);
            var isDir = (info.Attributes | FileAttributes.Directory) == FileAttributes.Directory;
            if (info.Exists == false && isDir == false) {
                return;
            }
            if (isDir) {
                while (Directory.Exists(path)) {
                    try {
                        Directory.Delete(path, true);
                    } catch {
                        Thread.Sleep(10);
                    }
                }
                return;
            }
            while (File.Exists(path)) {
                try {
                    File.Delete(path);
                } catch {
                    Thread.Sleep(10);
                }
            }
            return;
        }
        /// <summary>
        /// 安全的删除目录
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static async Task SafeRemoveFolderAsync(string filePath) {
            var info = new FileInfo(filePath);
            var isDir = (info.Attributes | FileAttributes.Directory) == FileAttributes.Directory;
            if (info.Exists == false && isDir == false) {
                return;
            }
            if (isDir) {
                while (Directory.Exists(filePath)) {
                    try {
                        Directory.Delete(filePath, true);
                    } catch {
                        await Task.Delay(10);
                    }
                }
            }

            while (File.Exists(filePath)) {
                try {
                    File.Delete(filePath);
                } catch {
                    await Task.Delay(10);
                }
            }
            return;
        }
    }
}